package com.candy.common.aspectj;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.candy.common.annotations.OpLog;
import com.candy.common.interfaces.OpLogSaveService;
import com.candy.common.manager.AsyncManager;
import com.candy.common.utils.CommonUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import cn.dev33.satoken.stp.StpUtil;

/**
 * 操作日志注解处理
 *
 * @author rong xi
 * @version 1.0
 * @date 2023/09/14 18:28
 */
@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class OpLogAspect {

    @Autowired(required = false)
    private OpLogSaveService opLogSaveService;

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");

    /**
     * 处理请求前执行
     */
    @Before("@annotation(controllerLog)")
    public void boBefore(JoinPoint joinPoint, OpLog controllerLog) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 切点
     */
    @Pointcut("@annotation(com.candy.common.annotations.OpLog)")
    public void opLogAspect() {
    }

    /**
     * 正常返回处理
     *
     * @param joinPoint 切点
     * @param result 返回结果
     */
    @AfterReturning(pointcut = "opLogAspect()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        recordOpLog(joinPoint, null, result);
    }

    /**
     * 出现异常处理
     *
     * @param joinPoint 切点
     * @param e 异常对象
     */
    @AfterThrowing(value = "opLogAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        recordOpLog(joinPoint, e, null);
    }

    /**
     * 记录日志
     *
     * @param joinPoint 切点
     * @param e 异常
     * @param result 返回结果
     */
    public void recordOpLog(JoinPoint joinPoint, Exception e, Object result) {
        try {
            OpLog annotation = getOpLogAnnotation(joinPoint);
            if (annotation == null) {
                return;
            }

            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();
            final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
            Long userId = StpUtil.getLoginIdAsLong();
            log.info("annotation:{}",annotation);
            JSONObject logObject = new JSONObject();
            logObject.set("moduleName", annotation.module());
            logObject.set("type", annotation.action());
            logObject.set("describe", annotation.describe());
            if(StrUtil.isBlank(annotation.describe())){
                logObject.set("describe", annotation.action().getTitle()+annotation.module());
            }
            logObject.set("requestIp", CommonUtil.getRequestIp(request));
            logObject.set("requestMethod", Objects.requireNonNull(request).getMethod());
            logObject.set("requestUrl", request.getRequestURL());
            logObject.set("requestTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            logObject.set("userId", userId);
            logObject.set("clientBrowser",userAgent.getBrowser()+userAgent.getVersion());
            logObject.set("clientOs",userAgent.getOs()+userAgent.getOsVersion());

            if (annotation.saveRequest()) {
                try {
                    logObject.set("requestParam", (CommonUtil.isMultipart(request) ? "" : JSON.toJSONString(joinPoint.getArgs())));
                } catch (Exception e1) {
                    log.error("读取请求参数异常:", e1);
                }
            }
            //有异常
            if (e != null) {
                logObject.set("errorMsg", StrUtil.sub(e.getMessage(), 0, 2000));
            }
            //有返回结果
            if (annotation.saveResponse() && result != null) {
                logObject.set("responseContent", StrUtil.sub(JSON.toJSONString(result), 0, 2000));
            }
            //设置响应时间
            logObject.set("costTime", System.currentTimeMillis() - TIME_THREADLOCAL.get());

            if(opLogSaveService != null){
                AsyncManager.runTask(opLogSaveService.save(logObject));
            }

        }catch (Exception ex){
            log.error("记录日志出现异常:",ex);
        }finally {
            TIME_THREADLOCAL.remove();
        }
    }

    /**
     * 获取注解对象
     *
     * @param joinPoint 切点
     * @return 注解对象
     */
    private OpLog getOpLogAnnotation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(OpLog.class);
        }
        return null;
    }

}
